#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <memory.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include "devvideo.h"


int DevVideo_initialize(DevVideo *dev, DevVideoConfig *devConfig)
{
    struct v4l2_requestbuffers reqbuf;
    unsigned int i;

    /* initialize state */
    dev->isStreamOn = 0;

    /* open */
    dev->node = devConfig->node;
    dev->handle = open(dev->node, O_RDWR);
    if (dev->handle < 0) {
        perror("DevVideo_initialize: open error");
        return -1;
    }
    /* set input */
    if (devConfig->input != -1) {
        if (ioctl(dev->handle, VIDIOC_S_INPUT, &devConfig->input) < 0) {
            printf("DevVideo_setInput: VIDIOC_S_INPUT error");
            return -1;
        }
    }
    #if 0
    if ((ioctl(dev->handle, VIDIOC_G_FMT, &dev->vfmt)) < 0) {
        printf("DevVideo_initialize: VIDIOC_G_FMT error\n");
        return -1;
    }
    #endif
    /* set crop */
    if ((ioctl(dev->handle, VIDIOC_S_CROP, &devConfig->vcrop)) < 0) {
        printf("DevVideo_initialize: VIDIOC_S_CROP vcrop\n");
        return -1;
    }
    /* set format */
    memset(&dev->vfmt, 0, sizeof(dev->vfmt));
    if (devConfig->vfmt.type)
        dev->vfmt.type = devConfig->vfmt.type;
    else
        dev->vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (devConfig->vfmt.fmt.pix.width)
        dev->vfmt.fmt.pix.width = devConfig->vfmt.fmt.pix.width;
    if (devConfig->vfmt.fmt.pix.height)
        dev->vfmt.fmt.pix.height = devConfig->vfmt.fmt.pix.height;
    if (devConfig->vfmt.fmt.pix.pixelformat)
        dev->vfmt.fmt.pix.pixelformat = devConfig->vfmt.fmt.pix.pixelformat;
    if ((ioctl(dev->handle, VIDIOC_S_FMT, &dev->vfmt)) < 0) {
        printf("DevVideo_initialize: VIDIOC_S_FMT error\n");
        return -1;
    }
    if ((ioctl(dev->handle, VIDIOC_G_FMT, &dev->vfmt)) < 0) {
        printf("DevVideo_initialize: VIDIOC_S_FMT error\n");
        return -1;;
    }

    {
        if (dev->vfmt.type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
            struct v4l2_format vfmt1;

            memset(&vfmt1, 0, sizeof(vfmt1));
            vfmt1.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY;
            vfmt1.fmt.win.chromakey = 0x00000000;
            //vfmt1.fmt.win.global_alpha = 0xffffffff;
            vfmt1.fmt.win.w.height = 480;
            vfmt1.fmt.win.w.left = 0;
            vfmt1.fmt.win.w.top = 0;
            vfmt1.fmt.win.w.width = 640;
            if ((ioctl(dev->handle, VIDIOC_S_FMT, &vfmt1)) < 0) {
                printf("DevVideo_initialize: VIDIOC_S_FMT1 error\n");
                return -1;
            }

        }
    }



    /* set output */
    if (devConfig->output != -1) {
        if (ioctl(dev->handle, VIDIOC_S_OUTPUT, &devConfig->output) < 0) {
            printf("DevVideo_setInput: VIDIOC_S_OUTPUT error");
            return -1;
        }
    }
    /* request buffer */
    if (devConfig->bufferNumber == 0)
        dev->bufferNumber = 1;
    else
        dev->bufferNumber = devConfig->bufferNumber;
    reqbuf.count = dev->bufferNumber;
    reqbuf.type = dev->vfmt.type;
    reqbuf.memory = V4L2_MEMORY_MMAP;
    if(ioctl(dev->handle, VIDIOC_REQBUFS, &reqbuf) < 0) {
        printf("DevVideo_initialize: VIDIOC_REQBUFS\n");
        return -1;
    }
    if (reqbuf.count != dev->bufferNumber) {
        printf("DevVideo_initialize: reqbuf bufferNumber error\n");
        return -1;
    }
    /* mmap */
    dev->buffer = (Buffer*)calloc(dev->bufferNumber, sizeof(Buffer));
    if (NULL == dev->buffer) {
        printf("DevVideo_initialize: calloc error");
        return -1;
    }
    for(i = 0; i < dev->bufferNumber; i++) {
        struct v4l2_buffer buf;
        buf.type = reqbuf.type;
        buf.index = i;
        buf.memory = reqbuf.memory;
        if(ioctl(dev->handle, VIDIOC_QUERYBUF, &buf) < 0) {
            printf("DevVideo_initialize: VIDIOC_QUERYBUF error %d\n", 1);
            return -1;
        }
        dev->buffer[i].index = i;
        dev->buffer[i].length = buf.length;
        dev->buffer[i].paddr = buf.m.offset;
        dev->buffer[i].start = (char*)mmap(
                    NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, dev->handle, buf.m.offset);
        if (MAP_FAILED == dev->buffer[i].start) {
            printf("DevVideo_initialize: mmap error %d\n", i);
            return -1;
        }
        memset(dev->buffer[i].start, 0x00, dev->buffer[i].length);
        if(ioctl(dev->handle, VIDIOC_QBUF, &buf) < 0) {
            printf("DevVideo_initialize: VIDIOC_QBUF error\n");
            return -1;
        }
    }
    return 0;
}

int DevVideo_uninitialize(DevVideo *dev)
{
    int i;
    for (i = 0; i < (int)dev->bufferNumber; i++)
        munmap(dev->buffer[i].start, dev->buffer[i].length);
    close(dev->handle);
    return 0;
}

inline int DevVideo_streamOn(DevVideo *dev)
{
    int a = dev->vfmt.type;
    if (dev->isStreamOn)
        return 0;
    if (ioctl(dev->handle, VIDIOC_STREAMON, &a) < 0) {
        perror("DevVideo_streamOn: VIDIOC_STREAMON error\n");
        return -1;
    }
    dev->isStreamOn =1;
    return 0;
}

inline int DevVideo_streamOff(DevVideo *dev)
{
    int a = dev->vfmt.type;
    if (!dev->isStreamOn)
        return 0;
    if (ioctl(dev->handle, VIDIOC_STREAMOFF, &a) < 0) {
        perror("DevVideo_streamOn: VIDIOC_STREAMOFF error\n");
        return -1;
    }
    dev->isStreamOn = 0;
    return 0;
}

inline int DevVideo_dqBuffer(DevVideo *dev, struct v4l2_buffer *buffer)
{
    buffer->type   = dev->vfmt.type;
    buffer->index  = 0;
    buffer->memory = V4L2_MEMORY_MMAP;
    if (ioctl(dev->handle, VIDIOC_DQBUF, buffer) < 0) {
        printf("DevVideo_dqBuffer: VIDIOC_DQBUF error\n");
        return -1;
    }
    return 0;
}

inline int DevVideo_qBuffer(DevVideo *dev, struct v4l2_buffer *buffer)
{
    if (ioctl(dev->handle, VIDIOC_QBUF, buffer) < 0) {
        printf("DevVideo_dqBuffer: VIDIOC_DQBUF error\n");
        return -1;
    }
    return 0;
}
